/**
 *
 * Module        : Duplication
 * Authors       : Ben Kwint & Arie van der Veek
 * Date          : November 15th 2011
 * Description   : Calculates the code duplication within a java project.
 *                 This means the following:
 *
 *                    We calculate ode dulication as the percentage 
 *                    of all code that occurs more than once in equal 
 *                    code blocks of at least 6 lines.
 *
 *                 Taken directly from "A Practical Model for Measuring 
 *                 Maintainability" by Ilja Heitlager, Tobias Kuipers &
 *                 Joost Visser 
 *
 */
module Metrics::Duplication

import IO;
import List;
import Set;
import String;
import InOut::Input;
import lang::java::jdt::JDT;
import Constants;
import Time;

// The PreProcess functions removes all empty lines from the code and removes
// all the whitespace that is in the code that is not needed. This to make sure that
// we don't use those as duplication
public list[str] preProcess(list[str] lines)
{
  return for( s <- lines)
  {
    if( !isEmpty(trim(s)) ) 
    {
      append trim(s);
    }
  }
}

// The block generator generates all possible consecutive blocks
// of length lines of code
public list[str] blockGenerator(list[str] lines, int length)
{
  list[str] blocks = [];
  
  int count = 0;
  for( n <- [0..(size(lines) - (length+1))] )
  {
    str block = "";
    for( m <- [0..(length-1)])
    {
      block = block + lines[n+m] + "\n";
    }
    blocks = blocks + [block];
  }
  
  return blocks;
}

// Get the code blocks from all the given methods. This means that we only
// use code to detect code duplication that is inside a method declation.
// We do this because we are only interested in duplicate code, not duplicate
// variables in a class. A variable does not do any computation on it's own
// thus poses no danger to failty code
public list[str] getCodeBlocks(BindingRel methodDecls, int length)
{
  int startAllCodeFromMethods = getMilliTime();
  list[str] allCodeFromMethods= getAllCodeFromMethods(methodDecls);
  int endAllCodeFromMethods = getMilliTime();
  if ( Constants::SHOWTIME) println("Time spent on getting all code from methods <(endAllCodeFromMethods - startAllCodeFromMethods)/1000> seconds");
  int startPreProcces = getMilliTime();
  list[str] preProcessedMethods = 	preProcess(allCodeFromMethods);
  int endPreProcces = getMilliTime();
  if ( Constants::SHOWTIME) println("Time spent on Pre Proccessing code blocks <(endPreProcces - startPreProcces)/1000> seconds");
  int startGenerateCodeBlocks = getMilliTime();
  list[str] generatedBlocks = blockGenerator(preProcessedMethods, length);
  int endGenerateCodeBlocks = getMilliTime();
  if ( Constants::SHOWTIME) println("Time spent on generating code blocs <(endGenerateCodeBlocks - startGenerateCodeBlocks)/1000> seconds");
  return generatedBlocks;
}

// The actual calculation of the duplicate lines of code
// This function calculates all lines of codes that are duplicates 
// of other lines of code.
// So if [x..y] is a duplicate of [a..b] the [a..b] is also a duplicate of
// [x..y]
public int calculateDuplication(BindingRel methodDecls, int length)
{
  list[str] blocks = getCodeBlocks(methodDecls, length);
  
  int startCountingCodeDup = getMilliTime();
  
  int lastLookCounted = 0;
  int duplicateLines = 0;
  for( n <- [0..(size(blocks)-1)])
  {
    list[str] current = [blocks[n]];
    if( size( blocks & current ) > 1 )
    {
      duplicateLines = duplicateLines + (length-lastLookCounted);
      // we use this to keep track of how many lines of the current code block
      // are already listed as duplicate code to make sure that we don't count
      // code twice as duplicate code
      lastLookCounted = lastLookCounted + (length-lastLookCounted);
    }
    
    lastLookCounted = lastLookCounted - 1;
    if( lastLookCounted < 0 ) { lastLookCounted = 0; }
  }
  
  int endCountingCodeDup = getMilliTime();
  if ( Constants::SHOWTIME) println("Time spent on Counting Duplicate loop <(endCountingCodeDup - startCountingCodeDup)/1000> seconds");
  
  return duplicateLines;
}